// newCollision.cpp : Defines the entry point for the console application.
//
#include <stdafx.h>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <set>
#include <stdlib.h>
#include <vector>
#include <GL/glut.h>
#include <GL/glui.h>
#include "vec3f.h"

using namespace std;

const float GRAVITY = 8.0f;
const float boxSize = 10.0f;
float sRadius;
//The amount of time between each time that we handle collisions and apply the
//effects of gravity
const float timeBetUpdate = 0.01f;
const int TIMER_MS = 25; //The number of milliseconds to which the timer is set

//ball structure
struct bSphere 
{
	Vec3f v; //Velocity
	Vec3f pos; //Position
	float r; //Radius
	Vec3f color;
};

enum Wall {WALL_LEFT, WALL_RIGHT, WALL_FAR, WALL_NEAR, WALL_TOP, WALL_BOTTOM};

//stores a bSphere pair
struct bSpherePair 
{
	bSphere* bsphere1;
	bSphere* bsphere2;
};

//stores bSphere and wall pair
struct bSphereWallPair 
{
	bSphere* bSphere;
	Wall wall;
};

//glut variable
int win,w,h;
int totalBalls = 0; //keep track of number of sphere
int vt; // for testing

//fps variable
int font=(int)GLUT_BITMAP_8_BY_13;
int bitmapHeight=13;
int frame,timer,timebase=0;
char s[30];

struct Vertex {double x, y, z, nx, ny, nz;};
struct Face {int v1, v2, v3;};
vector<Vertex> capVertices, tetraVertices, icoVertices;//vertices;
vector<Face> capFaces, tetraFaces, icoFaces;//faces

vector<bSphere*> _bsph; //All of the sphere in play
float _angle = 0.0f; //The camera angle
//The amount of time until performUpdate should be called
float _timeUntilUpdate = 0;

// GLUI objects declaration
GLUI_RadioGroup *radioMethod;
GLUI_RadioGroup *radioModel;
GLUI_Spinner    *Oct_depth;
GLUI_EditText   *edittext;
GLUI_Checkbox   *checkSphere;

// These are the live variables passed into GLUI
int   boundSphere = 0; // bound sphere not show by default
int obj1 = 0; // set method to space partition by default
int obj2 = 0; // set obj to cap by default
int octDepth = 6; //set oct tree depth to 6 by default

static int octTreeDepth = octDepth;
const int minSphperTree = 3;
const int maxSphperTree = 6;

//Returns a random float from 0 to < 1
float randomize() {
	return (float)rand() / ((float)RAND_MAX + 1);
}

//Oct tree structure and functions
class Octree 
{
	private:
		Vec3f corner1; //(minX, minY, minZ)
		Vec3f corner2; //(maxX, maxY, maxZ)
		Vec3f center;//((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2)
		
		Octree *children[2][2][2];
		//Whether this has children
		bool hasChildren;
		//The sphere in this, if this doesn't have any children
		set<bSphere*> sphere;
		//The depth of this in the tree
		int depth;
		//The number of sphere in this, including those stored in its children
		int numBalls;
		
		//Adds a sphere to or removes one from the children of this
		void filebSphere(bSphere* ball, Vec3f pos, bool addbSphere) {
			//find sphere in children
			for(int x = 0; x < 2; x++) {
				if (x == 0) {
					if (pos[0] - ball->r > center[0]) {
						continue;
					}
				}
				else if (pos[0] + ball->r < center[0]) {
					continue;
				}
				
				for(int y = 0; y < 2; y++) {
					if (y == 0) {
						if (pos[1] - ball->r > center[1]) {
							continue;
						}
					}
					else if (pos[1] + ball->r < center[1]) {
						continue;
					}
					
					for(int z = 0; z < 2; z++) {
						if (z == 0) {
							if (pos[2] - ball->r > center[2]) {
								continue;
							}
						}
						else if (pos[2] + ball->r < center[2]) {
							continue;
						}
						
						//Add or remove the sphere
						if (addbSphere) 
						{
							children[x][y][z]->add(ball);
						}
						else {
							children[x][y][z]->remove(ball, pos);
						}
					}
				}
			}
		}
		
		// creates children and move sphere into it
		void haveChildren() {
			for(int x = 0; x < 2; x++) {
				float minX;
				float maxX;
				if (x == 0) {
					minX = corner1[0];
					maxX = center[0];
				}
				else {
					minX = center[0];
					maxX = corner2[0];
				}
				
				for(int y = 0; y < 2; y++) {
					float minY;
					float maxY;
					if (y == 0) {
						minY = corner1[1];
						maxY = center[1];
					}
					else {
						minY = center[1];
						maxY = corner2[1];
					}
					
					for(int z = 0; z < 2; z++) {
						float minZ;
						float maxZ;
						if (z == 0) {
							minZ = corner1[2];
							maxZ = center[2];
						}
						else {
							minZ = center[2];
							maxZ = corner2[2];
						}
						
						children[x][y][z] = new Octree(Vec3f(minX, minY, minZ),
													   Vec3f(maxX, maxY, maxZ),
													   depth + 1);
					}
				}
			}
			
			//remove all sphere and add them to the new children
			for(set<bSphere*>::iterator it = sphere.begin(); it != sphere.end();
					it++) {
				bSphere* ball = *it;
				filebSphere(ball, ball->pos, true);
			}
			sphere.clear();
			
			hasChildren = true;
		}
		
		//adds all sphere to the specified set
		void collectbSpheres(set<bSphere*> &bs) {
			if (hasChildren) {
				for(int x = 0; x < 2; x++) {
					for(int y = 0; y < 2; y++) {
						for(int z = 0; z < 2; z++) {
							children[x][y][z]->collectbSpheres(bs);
						}
					}
				}
			}
			else {
				for(set<bSphere*>::iterator it = sphere.begin(); it != sphere.end();
						it++) {
					bSphere* ball = *it;
					bs.insert(ball);
				}
			}
		}
		
		// destroys the children, and moves all sphere in its descendants to the sphere set
		void removeChildren() 
		{
			//Move all sphere in descendants of this to the sphere set
			collectbSpheres(sphere);
			
			for(int x = 0; x < 2; x++) {
				for(int y = 0; y < 2; y++) {
					for(int z = 0; z < 2; z++) {
						delete children[x][y][z];
					}
				}
			}
			
			hasChildren = false;
		}
		
		//Removes the specified ball at the indicated position
		void remove(bSphere* ball, Vec3f pos) {
			numBalls--;
			
			if (hasChildren && numBalls < minSphperTree) {
				removeChildren();
			}
			
			if (hasChildren) {
				filebSphere(ball, pos, false);
			}
			else {
				sphere.erase(ball);
			}
		}
		
		void potSWCollision(vector<bSphereWallPair> &cs,
										 Wall w, char coord, int dir) {
			if (hasChildren) {
				//Recursively call potSWCollision on the correct
				//half of the children (e.g. if w is WALL_TOP, call it on
				//children above centerY)
				for(int dir2 = 0; dir2 < 2; dir2++) {
					for(int dir3 = 0; dir3 < 2; dir3++) {
						Octree *child;
						switch (coord) {
							case 'x':
								child = children[dir][dir2][dir3];
								break;
							case 'y':
								child = children[dir2][dir][dir3];
								break;
							case 'z':
								child = children[dir2][dir3][dir];
								break;
						}
						
						child->potSWCollision(cs, w, coord, dir);
					}
				}
			}
			else {
				//Add (ball, w) for all sphere in this
				for(set<bSphere*>::iterator it = sphere.begin(); it != sphere.end();
						it++) {
					bSphere* ball = *it;
					bSphereWallPair swp;
					swp.bSphere = ball;
					swp.wall = w;
					cs.push_back(swp);
				}
			}
		}
	public:
		//Constructs a new Octree.  c1 is (minX, minY, minZ), c2 is (maxX, maxY,
		//maxZ), and d is the depth, which starts at 1.
		Octree(Vec3f c1, Vec3f c2, int d) 
		{
			corner1 = c1;
			corner2 = c2;
			center = (c1 + c2) / 2;
			depth = d;
			numBalls = 0;
			hasChildren = false;
		}
		
		//Adds a ball to this
		void add(bSphere* ball) {
			numBalls++;
			if (!hasChildren && depth < octTreeDepth &&
				numBalls > maxSphperTree) {
				haveChildren();
			}
			
			if (hasChildren) {
				filebSphere(ball, ball->pos, true);
			}
			else {
				sphere.insert(ball);
			}
		}
		
		//Removes a ball from this
		void remove(bSphere* ball) {
			remove(ball, ball->pos);
		}
		
		// change sphere location
		void sphereMoved(bSphere* sphere, Vec3f oldPos) 
		{
			remove(sphere, oldPos);
			add(sphere);
		}
		
		//Adds potential sphere to collisions set
		void potSSCollision(vector<bSpherePair> &collisions) {
			if (hasChildren) {
				for(int x = 0; x < 2; x++) {
					for(int y = 0; y < 2; y++) {
						for(int z = 0; z < 2; z++) {
							children[x][y][z]->
								potSSCollision(collisions);
						}
					}
				}
			}
			else {
				//Add all pairs (ball1, ball2) from sphere
				for(set<bSphere*>::iterator it = sphere.begin(); it != sphere.end();
						it++) {
					bSphere* ball1 = *it;
					for(set<bSphere*>::iterator it2 = sphere.begin();
							it2 != sphere.end(); it2++) {
						bSphere* ball2 = *it2;
						//This test makes sure that we only add each pair once
						if (ball1 < ball2) {
							bSpherePair bp;
							bp.bsphere1 = ball1;
							bp.bsphere2 = ball2;
							collisions.push_back(bp);
						}
					}
				}
			}
		}
		
		//Adds potential ball-wall collisions to the specified set
		void potSWCollision(vector<bSphereWallPair> &collisions) 
		{
			potSWCollision(collisions, WALL_LEFT, 'x', 0);
			potSWCollision(collisions, WALL_RIGHT, 'x', 1);
			potSWCollision(collisions, WALL_BOTTOM, 'y', 0);
			potSWCollision(collisions, WALL_TOP, 'y', 1);
			potSWCollision(collisions, WALL_FAR, 'z', 0);
			potSWCollision(collisions, WALL_NEAR, 'z', 1);
		}
};

Octree* _octree; //An octree with all af the sphere

//Puts potential sphere-sphre collisions in potentialCollisions
void potSSCollision(vector<bSpherePair> &potentialCollisions, vector<bSphere*> &sphere, Octree* octree) 
{
	if (radioMethod->get_int_val() == 0)
	{
		//Fast method
		octree->potSSCollision(potentialCollisions);
	}
	else
		if (radioMethod->get_int_val() == 2)
		{	
			//Slow method
			for(unsigned int i = 0; i < sphere.size(); i++) 
			{
				for(unsigned int j = i + 1; j < sphere.size(); j++) 
				{
					bSpherePair bp;
					bp.bsphere1 = sphere[i];
					bp.bsphere2 = sphere[j];
					potentialCollisions.push_back(bp);
				}
			}
		}
}

//Puts potential sphere-wall collisions in potentialCollisions
void potSWCollision(vector<bSphereWallPair> &potentialCollisions, vector<bSphere*> &sphere, Octree* octree)
{
	if (radioMethod->get_int_val() == 0)
	{
		//Fast method
		octree->potSWCollision(potentialCollisions);
	}
	else
		if (radioMethod->get_int_val() == 2)
		{
			//Slow method
			Wall walls[] = {WALL_LEFT, WALL_RIGHT, WALL_FAR, WALL_NEAR, WALL_TOP, WALL_BOTTOM};
			for(unsigned int i = 0; i < sphere.size(); i++) 
			{
				for(int j = 0; j < 6; j++) 
				{
					bSphereWallPair swp;
					swp.bSphere = sphere[i];
					swp.wall = walls[j];
					potentialCollisions.push_back(swp);
				}
			}
		}
}

// moves all of the sphere by their velocity times dt
void movebSpheres(vector<bSphere*> &sphere, Octree* octree, float dt) 
{
	for(unsigned int i = 0; i < sphere.size(); i++) {
		bSphere* s = sphere[i];
		Vec3f oldPos = s->pos;
		s->pos += s->v * dt;
		octree->sphereMoved(s, oldPos);
	}
}

// reduces the y coordinate of the velocity of each sphere by GRAVITY * timeBetUpdate
void applyGravity(vector<bSphere*> &sphere) 
{
	for(unsigned int i = 0; i < sphere.size(); i++) 
	{
		bSphere* s = sphere[i];
		s->v -= Vec3f(0, GRAVITY * timeBetUpdate, 0);
	}
}

// determine whether two sphere are colliding
bool checkSSCollision(bSphere* s1, bSphere* s2) 
{
	//Check whether the sphere are close enough
	float r = s1->r + s2->r;
	if ((s1->pos - s2->pos).magnitudeSquared() < r * r) 
	{
		//Check whether the sphere are moving toward each other
		Vec3f netVelocity = s1->v - s2->v;
		Vec3f displacement = s1->pos - s2->pos;
		return netVelocity.dot(displacement) < 0;
	}
	else
		return false;
}

//Returns the direction from the origin to the wall
Vec3f wallDirection(Wall wall) 
{
	switch (wall) 
	{
		case WALL_LEFT:
			return Vec3f(-1, 0, 0);
		case WALL_RIGHT:
			return Vec3f(1, 0, 0);
		case WALL_FAR:
			return Vec3f(0, 0, -1);
		case WALL_NEAR:
			return Vec3f(0, 0, 1);
		case WALL_TOP:
			return Vec3f(0, 1, 0);
		case WALL_BOTTOM:
			return Vec3f(0, -1, 0);
		default:
			return Vec3f(0, 0, 0);
	}
}

// determin whether a ball and a wall are colliding
bool checkSWCollision(bSphere* sphere, Wall wall) 
{
	Vec3f dir = wallDirection(wall);
	//Check whether the ball is far enough in the "dir" direction, and whether
	//it is moving toward the wall
	return sphere->pos.dot(dir) + sphere->r > boxSize / 2 && sphere->v.dot(dir) > 0;
}
// do all S-S collisions
void doSSCollision(vector<bSphere*> &sphere, Octree* octree) 
{
	vector<bSpherePair> bps;
	potSSCollision(bps, sphere, octree);
	for(unsigned int i = 0; i < bps.size(); i++) 
	{
		bSpherePair bp = bps[i];
		
		bSphere* s1 = bp.bsphere1;
		bSphere* s2 = bp.bsphere2;
		if (checkSSCollision(s1, s2)) 
		{
			//Make the sphere reflect off of each other
			Vec3f displacement = (s1->pos - s2->pos).normalize();
			s1->v -= 2 * displacement * s1->v.dot(displacement);
			s2->v -= 2 * displacement * s2->v.dot(displacement);
		}
	}
}

//  all sphere-wall collisions
void doSWCollision(vector<bSphere*> &sphere, Octree* octree) 
{
	vector<bSphereWallPair> swps;
	potSWCollision(swps, sphere, octree);
	for(unsigned int i = 0; i < swps.size(); i++) 
	{
		bSphereWallPair swp = swps[i];
		
		bSphere* s = swp.bSphere;
		Wall w = swp.wall;
		if (checkSWCollision(s, w)) 
		{
			//Make the ball reflect off of the wall
			Vec3f dir = (wallDirection(w)).normalize();
			s->v -= 2 * dir * s->v.dot(dir);
		}
	}
}

// add gravity and check all collisions.  Should be called every timeBetUpdate seconds.
void performUpdate(vector<bSphere*> &sphere, Octree* octree) 
{
	applyGravity(sphere);
	doSSCollision(sphere, octree);
	doSWCollision(sphere, octree);
}

//Advances the objects by t.  timeUntilUpdate is the amount of time until the next call to performUpdate.
void advance(vector<bSphere*> &sphere, Octree* octree, float t, float &timeUntilUpdate) 
{
	while (t > 0) 
	{
		if (timeUntilUpdate <= t)
		{
			movebSpheres(sphere, octree, timeUntilUpdate);
			performUpdate(sphere, octree);
			t -= timeUntilUpdate;
			timeUntilUpdate = timeBetUpdate;
		}
		else 
		{
			movebSpheres(sphere, octree, t);
			timeUntilUpdate -= t;
			t = 0;
		}
	}
}

//Deletes everything.  This should be called when exiting the program.
void cleanup() 
{
	for(unsigned int i = 0; i < _bsph.size(); i++) 
	{
		delete _bsph[i];
	}
	delete _octree;
}

void addSphere()
{
	for(int i = 0; i < 5; i++) 
	{
		bSphere* ball = new bSphere();
		ball->pos = Vec3f(8 * randomize() - 4,
						  8 * randomize() - 4,
						  8 * randomize() - 4);
		ball->v = Vec3f(8 * randomize() - 4,
						8 * randomize() - 4,
						8 * randomize() - 4);
		ball->r = sRadius; // max radius of object
		ball->color = Vec3f(0.6f * randomize() + 0.2f,
							0.6f * randomize() + 0.2f,
							0.6f * randomize() + 0.2f);
		_bsph.push_back(ball);
		_octree->add(ball);
	}
	totalBalls += 5;
}

void fillVertices(const char *fileName)
{
	char type;
	float x, y, z, nx, ny, nz;
	int vid, fid, v1, v2, v3;

	FILE *f;

	fopen_s(&f,fileName, "r");

	while ((type = fgetc(f)) != EOF)
	{
		switch (type)
		{
			case 'V':
				if (fscanf_s(f,"ertex %d %f %f %f {normal=(%f %f %f)}\n", &vid, &x, &y, &z, &nx, &ny, &nz) == 7)
				{
					Vertex vertex;
					vertex.x = x;
					vertex.y = y;
					vertex.z = z;
					vertex.nx = nx;
					vertex.ny = ny;
					vertex.nz = nz;

					if (radioModel->get_int_val() == 0)
					{
						capVertices.push_back(vertex);
					}
					else if (radioModel->get_int_val() == 1)
					{
						tetraVertices.push_back(vertex);
					}
					else if (radioModel->get_int_val() == 2)
					{
						icoVertices.push_back(vertex);
					}
				}
				break;

			case 'F':
				if (fscanf_s(f, "ace %d %d %d %d\n", &fid, &v1, &v2, &v3) == 4)
				{
					Face face;
					face.v1 = v1 - 1;
					face.v2 = v2 -1 ;
					face.v3 = v3 - 1;

					if (radioModel->get_int_val() == 0)
					{
						capFaces.push_back(face);
					}
					else if (radioModel->get_int_val() == 1)
					{
						tetraFaces.push_back(face);
					}
					else if (radioModel->get_int_val() == 2)
					{
						icoFaces.push_back(face);
					}
				}
				break;

			default:
				do {type = fgetc(f);} while (type != EOF && type != '\n');
				break;
		}
	}
}

void LoadModel()
{
	const char *fileName = "";
	int selectedModel;

	// Determine which file to load
	selectedModel = radioModel->get_int_val();
	switch (selectedModel)
	{
		case 0:
			{
				fileName = "cap.m";
				sRadius = 0.55;
				fillVertices(fileName);
				break;
			}
		case 1:
			{
				fileName = "tetra.m";
				sRadius = 0.85;
				fillVertices(fileName);
				break;
			}
		case 2:
			{
				fileName = "icosahedron.m";
				sRadius = 0.42;
				fillVertices(fileName);
				break;
			}
		case 3:
			{
				fileName = "gargoyle.m";
				break;
			}
		case 4:
			{
				fileName = "knot.m";
				break;
			}
		case 5:
			{
				fileName = "lion.m";
				break;
			}
	}
}

void myKeypress(unsigned char key, int x, int y) 
{
	switch (key) {
		case 27: //Escape key
			cleanup();
			exit(0);
		case ' ':
			//Add 5 spheres
			addSphere();
			edittext->set_int_val(totalBalls);
			break;
	}
}

void initRendering() 
{
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	glEnable(GL_NORMALIZE);
	glEnable(GL_COLOR_MATERIAL);	
}

void myResize(int w1, int h1)
{
	if(h1 == 0)
		h1 = 1;

	w = w1;
	h = h1;
	float ratio = 1.0f * w / h;

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

    glViewport(0, 0, w, h);
	gluPerspective(45,ratio,0.1,1000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0, 0, 0, 0,0,0, 0.0f,1.0f,0.0f);
}

void setOrthographicProjection() 
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	glLoadIdentity();
	gluOrtho2D(0, w, 0, h);

	glScalef(1, -1, 1);
	glTranslatef(0, -h, 0);
	glMatrixMode(GL_MODELVIEW);
}

void resetPerspectiveProjection() 
{
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

void renderBitmapString(float x, float y, void *font,char *string)
{
  char *c;

  glRasterPos2f(x, y);
  for (c=string; *c != '\0'; c++) 
  {
    glutBitmapCharacter(font, *c);
  }
}

void dispFps()
{
	//display fps
	frame++;
	timer=glutGet(GLUT_ELAPSED_TIME);
	if (timer - timebase > 1000) {
		sprintf(s,"FPS:%4.2f",frame*1000.0/(timer-timebase));
		timebase = timer;		
		frame = 0;
	}

	glColor3f(0.0f,1.0f,1.0f);
	setOrthographicProjection();
	glPushMatrix();
	glLoadIdentity();
	renderBitmapString(30,35,(void *)font,s);
	renderBitmapString(30,55,(void *)font,"Esc - Quit");
	glPopMatrix();
	resetPerspectiveProjection();
}

void drawModel(float rad)
{
	glShadeModel(GL_SMOOTH);
	//glColor3f(1, 0, 0);

	if (rad == 0.55f)
	{	
		glBegin(GL_TRIANGLES);
		for (int i = 0; i < (int)capFaces.size(); i++)
		{
			Face f = capFaces[i];
			Vertex v1 = capVertices[f.v1];
			Vertex v2 = capVertices[f.v2];
			Vertex v3 = capVertices[f.v3];

			glNormal3f(v1.nx, v1.ny, v1.nz);
			glVertex3f(v1.x, v1.y, v1.z);

			glNormal3f(v2.nx, v2.ny, v2.nz);
			glVertex3f(v2.x, v2.y, v2.z);

			glNormal3f(v3.nx, v3.ny, v3.nz);
			glVertex3f(v3.x, v3.y, v3.z);
		}
		glEnd();
	}
	else if (rad == 0.85f)
	{
		glBegin(GL_TRIANGLES);
		for (int i = 0; i < (int)tetraFaces.size(); i++)
		{
			Face f = tetraFaces[i];
			Vertex v1 = tetraVertices[f.v1];
			Vertex v2 = tetraVertices[f.v2];
			Vertex v3 = tetraVertices[f.v3];

			glNormal3f(v1.nx, v1.ny, v1.nz);
			glVertex3f(v1.x, v1.y, v1.z);

			glNormal3f(v2.nx, v2.ny, v2.nz);
			glVertex3f(v2.x, v2.y, v2.z);

			glNormal3f(v3.nx, v3.ny, v3.nz);
			glVertex3f(v3.x, v3.y, v3.z);
		}
		glEnd();
	}
	else if (rad == 0.42f)
	{
		glBegin(GL_TRIANGLES);
		for (int i = 0; i < (int)icoFaces.size(); i++)
		{
			Face f = icoFaces[i];
			Vertex v1 = icoVertices[f.v1];
			Vertex v2 = icoVertices[f.v2];
			Vertex v3 = icoVertices[f.v3];

			glNormal3f(v1.nx, v1.ny, v1.nz);
			glVertex3f(v1.x, v1.y, v1.z);

			glNormal3f(v2.nx, v2.ny, v2.nz);
			glVertex3f(v2.x, v2.y, v2.z);

			glNormal3f(v3.nx, v3.ny, v3.nz);
			glVertex3f(v3.x, v3.y, v3.z);
		}
		glEnd();
	}
	glutPostRedisplay();
}

void drawScene() 
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
	glTranslatef(0, 0, -20);
	glRotatef(-_angle, 1, 1, 0);
	
	GLfloat ambientColor[] = {0.5, 0.5, 0.5, 1.0};
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor);
	
	GLfloat lightColor[] = {0.7, 0.7, 0.7, 1};
	GLfloat lightPos[] = {1, 0.2, 0, 0};
	glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	glColor3f(0, 1, 1);

	//Draw the top and the bottom of the box
	//bottom
	glBegin(GL_LINE_LOOP);
		glVertex3f(-boxSize / 2, -boxSize / 2, -boxSize / 2);
		glVertex3f(-boxSize / 2, -boxSize / 2, boxSize / 2);
		glVertex3f(boxSize / 2, -boxSize / 2, boxSize / 2);
		glVertex3f(boxSize / 2, -boxSize / 2, -boxSize / 2);
	glEnd();

	//top		
	glBegin(GL_LINE_LOOP);

		glVertex3f(-boxSize / 2, boxSize / 2, -boxSize / 2);
		glVertex3f(boxSize / 2, boxSize / 2, -boxSize / 2);
		glVertex3f(boxSize / 2, boxSize / 2, boxSize / 2);
		glVertex3f(-boxSize / 2, boxSize / 2, boxSize / 2);
	glEnd();

	glShadeModel(GL_SMOOTH);
	glDisable(GL_TEXTURE_2D);

	//draw box sides except top and bottom with lines (no texture)
	glBegin(GL_LINES);
		glColor3f(1, 1, 1);
		glVertex3f(-boxSize / 2, boxSize / 2, -boxSize / 2); glVertex3f(-boxSize / 2, -boxSize / 2, -boxSize / 2);
		glVertex3f( boxSize / 2, boxSize / 2, -boxSize / 2); glVertex3f( boxSize / 2, -boxSize / 2, -boxSize / 2);
		glVertex3f( boxSize / 2, boxSize / 2,  boxSize / 2); glVertex3f( boxSize / 2, -boxSize / 2,  boxSize / 2);
		glVertex3f(-boxSize / 2, boxSize / 2,  boxSize / 2); glVertex3f(-boxSize / 2, -boxSize / 2,  boxSize / 2);
	glEnd();
	
	//draw objects
	for(unsigned int i = 0; i < _bsph.size(); i++) 
	{
		bSphere* ball = _bsph[i];
		glPushMatrix();
		glTranslatef(ball->pos[0], ball->pos[1], ball->pos[2]);
		glColor3f(ball->color[0], ball->color[1], ball->color[2]);

		drawModel(ball->r);

		// draw bounding sphere if checked
		if (checkSphere->get_int_val() == 1)
		{	
			glutWireSphere(ball->r, 12, 12); //Draw a sphere
		}
		glPopMatrix();
	}

	dispFps();
	
	glutSwapBuffers();
	glutPostRedisplay(); 
}

//Called every TIMER_MS milliseconds
void update(int value) 
{
	advance(_bsph, _octree, (float)TIMER_MS / 1000.0f, _timeUntilUpdate);
	_angle += (float)TIMER_MS / 100;
	if (_angle > 360) 
	{
		_angle -= 360;
	}
	
	glutTimerFunc(TIMER_MS, update, 0);
}

void control_cb(int control)
{
	switch (control)
	{
	case 0:
		cleanup();
		break;
	case 2: // reset sphere
		_bsph.clear(); //clear ball vector
		delete _octree; // clear oct tree
		_octree = new Octree(Vec3f(-boxSize / 2, -boxSize / 2, -boxSize / 2), // re-create new oct tree
						 Vec3f(boxSize / 2, boxSize / 2, boxSize / 2), 1);
		totalBalls = 0;
		edittext->set_int_val(totalBalls);
		break;
	case 3: // add 50 sphere
		addSphere();
		edittext->set_int_val(totalBalls);
		break;
	case 4: // change oct tree depth
		octTreeDepth = octDepth;
		break;
	case 6:
		LoadModel();
		break;
	default: 
		glutPostRedisplay();
		break;
	}
}

void initGlut()
{
	glutDisplayFunc(drawScene);
	glutKeyboardFunc(myKeypress);
	glutReshapeFunc(myResize);
	glutTimerFunc(TIMER_MS, update, 0);
}

void initGlui()
{
	// create UI window using GLUI;
	GLUI *glui = GLUI_Master.create_glui("GUI", 0, 910, 0); // create separate window
	//GLUI *glui = GLUI_Master.create_glui_subwindow(win, GLUI_SUBWINDOW_RIGHT);  // create menu that dock in glut window

	// Radio group for choosing collision detection method
	GLUI_Panel *method_panel = new GLUI_Panel(glui, "Methods", 1);
	radioMethod = new GLUI_RadioGroup(method_panel,&obj1,1,control_cb);
	new GLUI_RadioButton(radioMethod, "Space Partition");
	new GLUI_RadioButton(radioMethod, "???");
	new GLUI_RadioButton(radioMethod, "None");

	new GLUI_Separator(glui);

	edittext = new GLUI_EditText(glui, "Objects:", 1, 5, control_cb);

	new GLUI_Separator(glui);

	GLUI_Panel *model_panel = new GLUI_Panel(glui, "Models", 1);
	radioModel = new GLUI_RadioGroup(model_panel,&obj2,6,control_cb);
	new GLUI_RadioButton(radioModel, "Cap");
	new GLUI_RadioButton(radioModel, "Tetrahadron");
	new GLUI_RadioButton(radioModel, "Icosahedron");

	new GLUI_Button(glui, "Reset Objects", 2, control_cb);
	new GLUI_Button(glui, "Add 5 Objects", 3, control_cb);

	new GLUI_Separator(glui);

	checkSphere = new GLUI_Checkbox(glui, "Bound Sphere", &boundSphere, 7, control_cb);

	new GLUI_Separator(glui);

	//// Spinner to set depth of Oct tree
	GLUI_Panel *depth_panel = new GLUI_Panel(glui, "Depth of Space Partition");
	Oct_depth  = new GLUI_Spinner(depth_panel, "Depth:", &octDepth, 4, control_cb);
	Oct_depth->set_int_limits(1, 16, 1);

	new GLUI_Separator(glui);

	new GLUI_Button(glui, "Quit", 0,(GLUI_Update_CB)exit);

	glui->set_main_gfx_window(win);
	GLUI_Master.set_glutIdleFunc(NULL);
}

int main(int argc, char* argv[])
{
	srand((unsigned int)time(0)); //Seed the random number generator
	
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	glutInitWindowSize(900, 900);
	
	win = glutCreateWindow("Collision Detection");
	initRendering();
	
	_octree = new Octree(Vec3f(-boxSize / 2, -boxSize / 2, -boxSize / 2),
						 Vec3f(boxSize / 2, boxSize / 2, boxSize / 2), 1);
	
	//register callback for glut
	initGlut();

	initGlui();

	LoadModel();

	glutMainLoop();
	return 1;
}









